<template>
  <el-form
    ref="formRef"
    :model="formData"
    :rules="formRules"
    label-width="100px"
    class="w-full max-w-4xl mx-auto"
  >
    <div
      v-for="(field, index) in fields"
      :key="index"
      class="form-item-wrapper relative mb-6 group"
    >
      <el-form-item :label="field.label" :prop="field.prop" class="mb-0">
        <component
          :is="getComponentType(field.type)"
          v-model="formData[field.prop]"
          :field="field"
        />
      </el-form-item>
      <div
        class="operator absolute right-0 top-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
      >
        <el-button
          :icon="Edit"
          circle
          size="small"
          type="primary"
          class="mr-2"
          @click="edit(field)"
        />
        <el-button
          :icon="Delete"
          circle
          size="small"
          type="danger"
          @click="del(field)"
        />
      </div>
    </div>
  </el-form>
</template>

<script setup lang="ts">
import { Edit, Delete } from "@element-plus/icons-vue";
import { ref, reactive, watch } from "vue";
import type { FormInstance } from "element-plus";
import { arrType, boolType, componentMap, FormField, Type } from "./utils";

export interface DynamicFormExpose {
  getData: () => Promise<Record<string, any> | undefined>;
  formRef: FormInstance | undefined;
}

interface Props {
  fields: FormField[];
}
interface Emits {
  (e: "submit", formData: Record<string, any>): void;
  (e: "edit", field: FormField): void;
  (e: "del", field: FormField): void;
}

const props = defineProps<Props>();
const emit = defineEmits<Emits>();

const formRef = ref<FormInstance>();
const formData = reactive<Record<string, any>>({});
const formRules = ref({});
const getValue = (type: Type) => {
  if (arrType.includes(type)) {
    return [];
  }
  if (type === "input-number") {
    return 0;
  }
  if (boolType.includes(type)) {
    return false;
  }
  return "";
};

const initForm = () => {
  const rules: Record<string, any> = {};
  props.fields.forEach(field => {
    formData[field.prop] = getValue(field.type);
    if (field.required) {
      rules[field.prop] = [
        {
          required: true,
          message: `请输入${field.label}`,
          trigger: "change"
        }
      ];
    }
    if (field.rules?.length) {
      rules[field.prop] = [
        ...(rules[field.prop] || []),
        ...field.rules.map(el => ({
          validator: (rule: any, value: any, callback: any) => {
            const rex = new RegExp(el.rule);
            if (rex.test(value)) {
              return callback();
            } else {
              return callback(new Error(el.message));
            }
          },
          trigger: "blur"
        }))
      ];
    }
  });
  formRules.value = rules;
};
watch(
  () => props.fields,
  () => {
    initForm();
  },
  {
    deep: true,
    immediate: true
  }
);

const getComponentType = (type: string) => {
  return componentMap[type as keyof typeof componentMap];
};

const getData = () => {
  if (!formRef.value) return;
  return new Promise((resolve, reject) => {
    formRef.value.validate(valid => {
      if (valid) {
        resolve(formData);
      } else {
        reject("表单验证失败");
      }
    });
  });
};

// 编辑
const edit = (field: FormField) => {
  emit("edit", field);
};
// 删除
const del = (field: FormField) => {
  emit("del", field);
};

defineExpose({
  getData,
  formRef
});
</script>

<style scoped>
/* 在小屏幕上调整布局 */
@media (width <= 768px) {
  .form-item-wrapper {
    padding-right: 0;
  }

  .operator {
    top: 0;
    right: 0;
    transform: none;
  }
}

.form-item-wrapper {
  padding-right: 80px; /* 为操作按钮预留空间 */
}

.operator {
  top: 50%;
  right: -80px; /* 将操作按钮移到表单项右侧 */
  transform: translateY(-50%);
}
</style>
